﻿using PMS.Core.Caching;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Utils;
using PMS.Data.DbUtils;
using PMS.Data.Entities.NaviMenus;
using PMS.Data.Entities.Security;
using PMS.Data.Entities.Settings;
using PMS.Data.Entities.Stores;
using PMS.Services.Caching;
using PMS.Services.Caching.Extensions;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Security;
using PMS.Services.EntityServices.Stores;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PMS.Services.EntityServices.NaviMenus
{
    public class NaviMenuService : INaviMenuService
    {
        #region Fields
        private readonly CatalogSettings _catalogSettings;
        private readonly IAclService _aclService;
        private readonly IWorkContext _workContext;
        private readonly IStoreContext _storeContext;
        private readonly ICacheKeyService _cacheKeyService;
        private readonly ICustomerService _customerService;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly IStaticCacheManager _staticCacheManager;
        private readonly IStoreMappingService _storeMappingService;
        private readonly IRepository<AclRecord> _aclRepository;
        private readonly IRepository<StoreMapping> _storeMappingRepository;
        private readonly IRepository<NaviMenu> _naviMenuRepository;
        private readonly IRepository<NaviMenuGroup> _naviMenuGroupRepository;
        private readonly IRepository<NaviMenuLayoutTemplate> _naviMenuTemplateRepository;
        private readonly IRepository<NaviMenuNaviMenuTemplateMapping> _naviMenuTemplateMappingRepository;
        #endregion

        #region Ctor
        public NaviMenuService(CatalogSettings catalogSettings,
            IAclService aclService,
            IWorkContext workContext,
            IStoreContext storeContext,
            ICacheKeyService cacheKeyService,
            ICustomerService customerService,
            IEventDispatcher eventDispatcher,
            IStaticCacheManager staticCacheManager,
            IStoreMappingService storeMappingService,
            IRepository<AclRecord> aclRepository,
            IRepository<StoreMapping> storeMappingRepository,
            IRepository<NaviMenu> naviMenuRepository,
            IRepository<NaviMenuGroup> naviMenuGroupRepository,
            IRepository<NaviMenuLayoutTemplate> naviMenuTemplateRepository,
            IRepository<NaviMenuNaviMenuTemplateMapping> naviMenuTemplateMappingRepository)
        {
            _catalogSettings = catalogSettings;
            _aclService = aclService;
            _workContext = workContext;
            _storeContext = storeContext;
            _cacheKeyService = cacheKeyService;
            _customerService = customerService;
            _eventDispatcher = eventDispatcher;
            _staticCacheManager = staticCacheManager;
            _storeMappingService = storeMappingService;

            _aclRepository = aclRepository;
            _naviMenuGroupRepository = naviMenuGroupRepository;
            _storeMappingRepository = storeMappingRepository;
            _naviMenuRepository = naviMenuRepository;
            _naviMenuTemplateRepository = naviMenuTemplateRepository;
            _naviMenuTemplateMappingRepository = naviMenuTemplateMappingRepository;
        }
        #endregion

        #region Utils
        #endregion

        #region Methods

        #region NaviMenuGroups
        public virtual IList<NaviMenuGroup> GetNaviMenuGroupsAll(bool showHidden = false)
        {
            var groupKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenuGroupsAllCacheKey);
            var query = _naviMenuGroupRepository.Table
                .Where(grp => showHidden || grp.IsActive)
                .OrderBy(grp => grp.DisplayOrder);
            return query.ToCachedList(groupKey);
        }

        public virtual NaviMenuGroup GetNaviMenuGroupById(int grpId)
        {
            if (grpId <= 0) return null;

            return _naviMenuGroupRepository.ToCachedGetById(grpId);
        }
        /// <summary>
        /// Get NaviMenuGroup By Predefined GroupName
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public virtual NaviMenuGroup GetNaviMenuGroupByName(MenuGroups groupName)
        {
            var nmGroups = GetNaviMenuGroupsAll(true);
            if (nmGroups == null || nmGroups.Count == 0)
                return null;
            var searchName = nameof(MenuGroups) + "." + Enum.ToObject(typeof(MenuGroups), groupName).ToString();
            return nmGroups.FirstOrDefault(grp => grp.GroupName.Equals(searchName, StringComparison.CurrentCultureIgnoreCase));
        }
        #endregion

        #region NaviMenuTemplate

        /// <summary>
        /// Delete NaviMenu template
        /// </summary>
        /// <param name="layoutTemplate">NaviMenuLayout template</param>
        public virtual void DeleteNaviMenutLayoutTemplate(NaviMenuLayoutTemplate layoutTemplate)
        {
            if (layoutTemplate == null)
                throw new ArgumentNullException(nameof(layoutTemplate));

            _naviMenuTemplateRepository.Delete(layoutTemplate);

            //event notification
            _eventDispatcher.EntityDeleted(layoutTemplate);
        }

        /// <summary>
        /// Gets all NaviMenu template
        /// </summary>
        /// <returns>NaviMenuLayout templates</returns>
        public virtual IList<NaviMenuLayoutTemplate> GetAllNaviMenuLayoutTemplates(bool showHidden = false)
        {
            var query = from pt in _naviMenuTemplateRepository.Table
                        where showHidden || pt.IsActive
                        orderby pt.TemplateType, pt.DisplayOrder
                        select pt;

            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenuLayoutTemplatesAllCacheKey);
            var templates = query.ToCachedList(key);

            return templates;
        }

        /// <summary>
        /// Gets a navimenu layout template
        /// </summary>
        /// <param name="layoutTemplateId">NaviMenuLayout template identifier</param>
        /// <returns>NaviMenuLayout template</returns>
        public virtual NaviMenuLayoutTemplate GetNaviMenuLayoutTemplateById(int layoutTemplateId)
        {
            if (layoutTemplateId == 0)
                return null;

            return _naviMenuTemplateRepository.ToCachedGetById(layoutTemplateId);
        }

        /// <summary>
        /// Inserts navimenu layout template
        /// </summary>
        /// <param name="layoutTemplate">NaviMenuLayout template</param>
        public virtual void InsertNaviMenuLayoutTemplate(NaviMenuLayoutTemplate layoutTemplate)
        {
            if (layoutTemplate == null)
                throw new ArgumentNullException(nameof(layoutTemplate));

            _naviMenuTemplateRepository.Insert(layoutTemplate);

            //event notification
            _eventDispatcher.EntityInserted(layoutTemplate);
        }

        /// <summary>
        /// Updates the navimenu layout template
        /// </summary>
        /// <param name="layoutTemplate">NaviMenuLayout template</param>
        public virtual void UpdateNaviMenuLayoutTemplate(NaviMenuLayoutTemplate layoutTemplate)
        {
            if (layoutTemplate == null)
                throw new ArgumentNullException(nameof(layoutTemplate));

            _naviMenuTemplateRepository.Update(layoutTemplate);

            //event notification
            _eventDispatcher.EntityUpdated(layoutTemplate);
        }

        #endregion

        #region NaviMenu-NaviMenuTemplate-Mapping
        /// <summary>
        /// Deletes a mapping
        /// </summary>
        /// <param name="mapping">NaviMenuNaviMenuTemplateMapping</param>
        public virtual void DeleteNaviMenuTemplateMapping(NaviMenuNaviMenuTemplateMapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException(nameof(mapping));

            _naviMenuTemplateMappingRepository.Delete(mapping);

            //event notification in NaviMenu Entity Inserted/Deleted/Updated
            //_eventDispatcher.EntityDeleted(mapping);
        }

        /// <summary>
        /// Gets NaviMenuTemplates collection by NaviMenu
        /// </summary>
        /// <param name="naviMenuId">NaviMenu identifier</param>
        /// <returns>NaviMenuLayoutTemplate collection</returns>
        public virtual IList<NaviMenuLayoutTemplate> GetNaviMenuLayoutTemplatesByNaviMenuId(int naviMenuId)
        {
            if (naviMenuId == 0)
                return new List<NaviMenuLayoutTemplate>();

            var query = from nmt in _naviMenuTemplateRepository.Table
                        join mp in _naviMenuTemplateMappingRepository.Table //.Where(m => m.NaviMenuId == naviMenuId)
                         on nmt.Id equals mp.NaviMenuTemplateId into tmpjoin
                        from mp in tmpjoin.DefaultIfEmpty()
                        where mp.NaviMenuId == naviMenuId
                        select nmt;

            return query.ToCachedList(CacheKeyConfigs.NaviMenuLayoutTemplatesPerMenuCacheKey);
        }

        /// <summary>
        /// Inserts a NaviMenu-NaviMenuTemplate mapping
        /// </summary>
        /// <param name="mapping">>NaviMenuTemplayMapping</param>
        public virtual void InsertNaviMenuTemplayMapping(NaviMenuNaviMenuTemplateMapping mapping)
        {
            if (mapping == null)
                throw new ArgumentNullException(nameof(mapping));

            _naviMenuTemplateMappingRepository.Insert(mapping);

            //event notification in NaviMenu Entity Inserted/Deleted/Updated
            //_eventDispatcher.EntityInserted(mapping);
        }

        #endregion

        #region NaviMenu

        /// <summary>
        /// Get a NaviMenu by identifier
        /// </summary>
        /// <param name="menuId"></param>
        /// <returns></returns>
        public virtual NaviMenu GetNaviMenuById(int menuId)
        {
            if (menuId <= 0)
                return null;

            return _naviMenuRepository.ToCachedGetById(menuId);
        }

        /// <summary>
        /// Gets NaviMenus by identifier
        /// </summary>
        /// <param name="menuIds">NaviMenu identifiers</param>
        /// <returns>NaviMenu</returns>
        public virtual List<NaviMenu> GetNaviMenusByIds(int[] menuIds)
        {
            if (menuIds == null || menuIds.Length == 0)
                return new List<NaviMenu>();

            var query = from p in _naviMenuRepository.Table
                        where menuIds.Contains(p.Id) && p.IsActive
                        select p;

            return query.ToList();
        }

        /// <summary>
        /// Inserts NaviMenu
        /// </summary>
        /// <param name="menu">NaviMenu</param>
        public virtual void InsertNaviMenu(NaviMenu menu)
        {
            if (menu == null)
                throw new ArgumentNullException(nameof(menu));

            _naviMenuRepository.Insert(menu);

            //event notification
            _eventDispatcher.EntityInserted(menu);
        }

        /// <summary>
        /// Updates the NaviMenu
        /// </summary>
        /// <param name="menu">NaviMenu</param>
        public virtual void UpdateNaviMenu(NaviMenu menu)
        {
            if (menu == null)
                throw new ArgumentNullException(nameof(menu));

            //validate category hierarchy
            var parentCategory = GetNaviMenuById(menu.ParentId);
            while (parentCategory != null)
            {
                if (menu.Id == parentCategory.Id)
                {
                    menu.ParentId = 0;
                    break;
                }

                parentCategory = GetNaviMenuById(parentCategory.ParentId);
            }

            _naviMenuRepository.Update(menu);

            //event notification
            _eventDispatcher.EntityUpdated(menu);
        }

        /// <summary>
        /// Delete NaviMenu
        /// </summary>
        /// <param name="menu">NaviMenu</param>
        public virtual void DeleteNaviMenu(NaviMenu menu)
        {
            if (menu == null)
                throw new ArgumentNullException(nameof(menu));

            //Delete NaviMenu-MenuTemplateMapping first
            var mtMappings = _naviMenuTemplateMappingRepository.Table.Where(m => m.NaviMenuId == menu.Id).ToList();
            if(mtMappings != null && mtMappings.Count > 0)
            {
                foreach (var mapping in mtMappings)
                {
                    DeleteNaviMenuTemplateMapping(mapping);
                }
            }
            _naviMenuRepository.Delete(menu);
            //event notification
            _eventDispatcher.EntityDeleted(menu);
            //event notification

            //reset a "Parent category" property of all child subcategories
            var submenus = GetAllNaviMenusByParentNaviMenuId(menu.Id, true);
            foreach (var subcategory in submenus)
            {
                subcategory.ParentId = 0;
                UpdateNaviMenu(subcategory);
            }
        }

        /// <summary>
        /// Delete NaviMenus
        /// </summary>
        /// <param name="menus">NaviMenus</param>
        public virtual void DeleteNaviMenus(IList<NaviMenu> menus)
        {
            if (menus == null)
                throw new ArgumentNullException(nameof(menus));

            foreach (var category in menus)
            {
                DeleteNaviMenu(category);
            }
        }
        /// <summary>
        /// Gets all NaviMenus Group by MenuGroup with all languages
        /// </summary>
        /// <param name="storeId">Store identifier; 0 if you want to get all records</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>Categories</returns>
        public virtual IList<NaviMenu> GetAllNaviMenusByMenuGroup(MenuGroups menuGrp, int storeId = 0, bool showHidden = false)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenusAllCacheKey,
                storeId,
                _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer),
                Enum.ToObject(typeof(MenuGroups), menuGrp).ToString(),
                showHidden);

            var grpMenus = _staticCacheManager.Get(key, () => GetAllNaviMenusByMenuGroup(menuGrp, storeId, 0, int.MaxValue, showHidden, "all").ToList());

            return grpMenus;
        }

        /// <summary>
        /// Gets all NaviMenus Group by MenuGroup
        /// </summary>
        /// <param name="menuGrp">MenuGroups</param>
        /// <param name="storeId">Store identifier; 0 if you want to get all records</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>NaviMenus</returns>
        public virtual IPagedList<NaviMenu> GetAllNaviMenusByMenuGroup(MenuGroups menuGrp, int storeId = 0,
            int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string lanCode = "cn")
        {
            var grpEntity = GetNaviMenuGroupByName(menuGrp);
            if (grpEntity == null || !grpEntity.IsActive)
                return null;

            return GetAllNaviMenusByMenuGroup(grpEntity.Id, storeId, pageIndex, pageSize, showHidden, lanCode);
        }

        /// <summary>
        /// Gets all NaviMenus Group by MenuGroupId
        /// </summary>
        /// <param name="menuGroupId">MenuGroups identified</param>
        /// <param name="storeId">Store identifier; 0 if you want to get all records</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>NaviMenus</returns>
        public virtual IPagedList<NaviMenu> GetAllNaviMenusByMenuGroup(int menuGroupId, int storeId = 0,
            int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string lanCode = "cn")
        {
            var query = _naviMenuRepository.Table.Where(menu => menu.MenuGroupId == menuGroupId);
            //参数 lanCode为空或 "all"时查询相同分组下的所有语言的导航菜单栏
            if(!string.IsNullOrEmpty(lanCode) && !lanCode.Equals("all", StringComparison.CurrentCultureIgnoreCase))
                query = query.Where(m => m.LanguageCode == lanCode.ToLower());
            if (!showHidden)
                query = query.Where(c => c.IsActive);
            query = query.OrderBy(c => c.MenuGroupId).ThenBy(c => c.LanguageCode).ThenBy(c => c.ParentId).ThenBy(c => c.DisplayOrder);

            if ((storeId > 0 && !_catalogSettings.IgnoreStoreLimitations) || (!showHidden && !_catalogSettings.IgnoreAcl))
            {
                if (!showHidden && !_catalogSettings.IgnoreAcl)
                {
                    //ACL (access control list)
                    var allowedCustomerRolesIds = _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer);
                    query = from c in query
                            join acl in _aclRepository.Table
                                on new { c1 = c.Id, c2 = nameof(NaviMenu) } equals new { c1 = acl.EntityId, c2 = acl.EntityName } into c_acl
                            from acl in c_acl.DefaultIfEmpty()
                            where !c.SubjectToAcl || allowedCustomerRolesIds.Contains(acl.CustomerRoleId)
                            select c;
                }

                if (storeId > 0 && !_catalogSettings.IgnoreStoreLimitations)
                {
                    //Store mapping
                    query = from c in query
                            join sm in _storeMappingRepository.Table
                                on new { c1 = c.Id, c2 = nameof(NaviMenu) } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into c_sm
                            from sm in c_sm.DefaultIfEmpty()
                            where !c.LimitedToStores || storeId == sm.StoreId
                            select c;
                }

                query = query.Distinct().OrderBy(c => c.MenuGroupId).ThenBy(c => c.LanguageCode).ThenBy(c => c.ParentId).ThenBy(c => c.DisplayOrder);
            }

            var unsortedCategories = query.ToList();

            //sort categories
            var sortedCategories = SortNaviMenusForTree(unsortedCategories);

            //paging
            return new PagedList<NaviMenu>(sortedCategories, pageIndex, pageSize);
        }
        /// <summary>
        /// Sort navimenus for tree representation
        /// </summary>
        /// <param name="source">Source</param>
        /// <param name="parentId">Parent NaviMenu identifier</param>
        /// <param name="ignoreCategoriesWithoutExistingParent">A value indicating whether categories without parent category in provided category list (source) should be ignored</param>
        /// <returns>Sorted NaviMenus</returns>
        public virtual IList<NaviMenu> SortNaviMenusForTree(IList<NaviMenu> source, int parentId = 0,
            bool ignoreCategoriesWithoutExistingParent = false)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            var result = new List<NaviMenu>();

            foreach (var cat in source.Where(c => c.ParentId == parentId).ToList())
            {
                result.Add(cat);
                result.AddRange(SortNaviMenusForTree(source, cat.Id, true));
            }

            if (ignoreCategoriesWithoutExistingParent || result.Count == source.Count)
                return result;

            //find categories without parent in provided category source and insert them into result
            foreach (var cat in source)
                if (result.FirstOrDefault(x => x.Id == cat.Id) == null)
                    result.Add(cat);

            return result;
        }

        /// <summary>
        /// Gets all NaviMenus filtered by parent NaviMenu identifier
        /// </summary>
        /// <param name="parentNaviMenuId">Parent category identifier</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>NaviMenus</returns>
        public virtual IList<NaviMenu> GetAllNaviMenusByParentNaviMenuId(int parentNaviMenuId, bool showHidden = false)
        {
            var key = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenusByParentNaviMenuIdCacheKey,
                           parentNaviMenuId, showHidden, _workContext.CurrentCustomer, _storeContext.CurrentStore);

            var query = _naviMenuRepository.Table;

            if (!showHidden)
                query = query.Where(c => c.IsActive);

            query = query.Where(c => c.ParentId == parentNaviMenuId);
            query = query.OrderBy(c => c.DisplayOrder).ThenBy(c => c.Id);

            if (!showHidden && (!_catalogSettings.IgnoreAcl || !_catalogSettings.IgnoreStoreLimitations))
            {
                if (!_catalogSettings.IgnoreAcl)
                {
                    //ACL (access control list)
                    var allowedCustomerRolesIds = _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer);
                    query = from c in query
                            join acl in _aclRepository.Table
                                on new
                                {
                                    c1 = c.Id,
                                    c2 = nameof(NaviMenu)
                                }
                                equals new
                                {
                                    c1 = acl.EntityId,
                                    c2 = acl.EntityName
                                }
                                into c_acl
                            from acl in c_acl.DefaultIfEmpty()
                            where !c.SubjectToAcl || allowedCustomerRolesIds.Contains(acl.CustomerRoleId)
                            select c;
                }

                if (!_catalogSettings.IgnoreStoreLimitations)
                {
                    //Store mapping
                    var currentStoreId = _storeContext.CurrentStore.Id;
                    query = from c in query
                            join sm in _storeMappingRepository.Table
                                on new
                                {
                                    c1 = c.Id,
                                    c2 = nameof(NaviMenu)
                                }
                                equals new
                                {
                                    c1 = sm.EntityId,
                                    c2 = sm.EntityName
                                }
                                into c_sm
                            from sm in c_sm.DefaultIfEmpty()
                            where !c.LimitedToStores || currentStoreId == sm.StoreId
                            select c;
                }

                query = query.Distinct().OrderBy(c => c.DisplayOrder).ThenBy(c => c.Id);
            }

            var childNaviMenus = query.ToCachedList(key);

            return childNaviMenus;
        }

        /// <summary>
        /// Gets child NaviMenus identifiers
        /// </summary>
        /// <param name="parentNaviMenuId">Parent NaviMenu identifier</param>
        /// <param name="storeId">Store identifier; 0 if you want to get all records</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>NaviMenus identifiers</returns>
        public virtual IList<int> GetChildNaviMenuIds(int parentNaviMenuId, int storeId = 0, bool showHidden = false)
        {
            var cacheKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenusChildIdentifiersCacheKey,
                parentNaviMenuId,
                _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer),
                _storeContext.CurrentStore,
                showHidden);

            return _staticCacheManager.Get(cacheKey, () =>
            {
                //little hack for performance optimization
                //there's no need to invoke "GetAllCategoriesByParentCategoryId" multiple times (extra SQL commands) to load childs
                //so we load all categories at once (we know they are cached) and process them server-side
                var parentMenu = GetNaviMenuById(parentNaviMenuId);
                if (parentMenu == null) return null;
                var menuIds = new List<int>();
                var grpmenus = GetAllNaviMenusByMenuGroup(parentMenu.MenuGroupId, storeId: storeId, showHidden: showHidden)
                    .Where(c => c.ParentId == parentNaviMenuId)
                    .Select(c => c.Id)
                    .ToList();
                menuIds.AddRange(grpmenus);
                menuIds.AddRange(grpmenus.SelectMany(cId => GetChildNaviMenuIds(cId, storeId, showHidden)));

                return menuIds;
            });
        }

        /// <summary>
        /// Gets all NaviMenus displayed on the home page
        /// </summary>
        /// <param name="menuGroups">null to get all navimenus</param>
        /// <param name="showHidden">A value indicating whether to show hidden records</param>
        /// <returns>NaviMenus</returns>
        public virtual IList<NaviMenu> GetAllNaviMenusDisplayedOnHomepage(MenuGroups? menuGroups = null, bool showHidden = false)
        {
            var query = _naviMenuRepository.Table;
            if (menuGroups.HasValue)
            {
                var grpEntity = GetNaviMenuGroupByName(menuGroups.Value);
                query = query.Where(m => m.MenuGroupId == grpEntity.Id);
            }
                
            query = from c in query
                    orderby c.MenuGroupId, c.DisplayOrder, c.Id
                    where c.IsActive && c.ShowOnHomepage
                    select c;

            var menus = query.ToCachedList(_cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenusAllDisplayedOnHomepageCacheKey));

            if (showHidden)
                return menus;

            var cacheKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenusDisplayedOnHomepageWithoutHiddenCacheKey,
                _storeContext.CurrentStore, _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer));

            var result = _staticCacheManager.Get(cacheKey, () =>
            {
                return menus
                    .Where(c => _aclService.Authorize(c) && _storeMappingService.Authorize(c))
                    .ToList();
            });

            return result;
        }

        /// <summary>
        /// Get formatted NaviMenu breadcrumb 
        /// Note: ACL and store mapping is ignored
        /// </summary>
        /// <param name="menu">NaviMenu</param>
        /// <param name="allMenus">All NaviMenu</param>
        /// <param name="separator">Separator</param>
        /// <returns>Formatted breadcrumb</returns>
        public virtual string GetFormattedBreadCrumb(NaviMenu menu, IList<NaviMenu> allMenus = null,
            string separator = " >> ")
        {
            var result = string.Empty;

            var breadcrumb = GetNaviMenuBreadCrumb(menu, allMenus, true);
            for (var i = 0; i <= breadcrumb.Count - 1; i++)
            {
                var displayName = breadcrumb[i].DisplayName;
                result = string.IsNullOrEmpty(result) ? displayName : $"{result}{separator}{displayName}";
            }

            return result;
        }

        /// <summary>
        /// Get NaviMenu breadcrumb 
        /// </summary>
        /// <param name="menu">NaviMenu</param>
        /// <param name="allMenus">All NaviMenu</param>
        /// <param name="showHidden">A value indicating whether to load hidden records</param>
        /// <returns>NaviMenu breadcrumb </returns>
        public virtual IList<NaviMenu> GetNaviMenuBreadCrumb(NaviMenu menu, IList<NaviMenu> allMenus = null, bool showHidden = false)
        {
            if (menu == null)
                throw new ArgumentNullException(nameof(menu));

            var breadcrumbCacheKey = _cacheKeyService.PrepareKeyForDefaultCache(CacheKeyConfigs.NaviMenuBreadcrumbCacheKey,
                menu,
                _customerService.GetCustomerRoleIds(_workContext.CurrentCustomer),
                _storeContext.CurrentStore,
                _workContext.WorkingLanguage);

            return _staticCacheManager.Get(breadcrumbCacheKey, () =>
            {
                var result = new List<NaviMenu>();

                //used to prevent circular references
                var alreadyProcessedCategoryIds = new List<int>();

                while (menu != null && //not null
                       menu.IsActive && //not deleted
                       (showHidden || _aclService.Authorize(menu)) && //ACL
                       (showHidden || _storeMappingService.Authorize(menu)) && //Store mapping
                       !alreadyProcessedCategoryIds.Contains(menu.Id)) //prevent circular references
                {
                    result.Add(menu);

                    alreadyProcessedCategoryIds.Add(menu.Id);

                    menu = allMenus != null
                        ? allMenus.FirstOrDefault(c => c.Id == menu.ParentId)
                        : GetNaviMenuById(menu.ParentId);
                }

                result.Reverse();

                return result;
            });
        }
        #endregion

        #endregion
    }
}
